home *** CD-ROM | disk | FTP | other *** search
/ MacFormat UK 176 / MF_UK_176_1.iso / DiscContents / In the mag / Widgets / BBC Radio Widget 2.2 / BBC Radio widget / BBC Radio.wdgt / BBC Radio.js < prev    next >
Encoding:
JavaScript  |  2006-09-09  |  66.9 KB  |  1,603 lines

  1. /*
  2.  
  3. BBC Radio widget by Hawkman. http://www.phantomgorilla.com/
  4. All suggestions and bug reports to hawkman@phantomgorilla.com please
  5.  
  6. *******************************************************************************
  7.  
  8. LICENCE
  9.  
  10. Free to use, free to look at and learn from, free to pinch small chunks of code from for use in your own projects.
  11.  
  12. However, you may NOT redistribute this widget, or a port or modified version of it, without obtaining permission from the author (via email, hawkman@phantomgorilla.com) and including credit to him in the finished project (a small notice in the widget's javascript/html files is all that's necessary; nothing visible on the widget).
  13.  
  14. This software is provided as-is, with no guarantee. The author can accept no responsibility for any consequences of its use.
  15.  
  16. *******************************************************************************
  17.  
  18. ABOUT THE WIDGET
  19.  
  20. - Basic Overview
  21.  
  22. This widget uses Real Player for the audio. Preferences are written upon the selection of an item in a pop-up menu, then this is compared to a list of stations to find the stream url. A function then writes html to the front of the widget to embed the realplayer plugin with the correct url, and changes the image source to reflect which station is playing.
  23.  
  24. - Credits
  25.  
  26. Basic Javascript routines taken out of Apple's sample Goodbye World widget. A small section of crash-preventing code is taken from Duncan Ponting's radio widget (http://www.bbc.co.uk/radio/prototypes/listenlive/) at his suggestion - cheers. Also taken from that widget is the idea to crawl the broswer plugins to find RealPlayer. Version string .plist lookup taken from Joshua Emmons' blank foo.wdgt (http://www.skia.net/). Study of his example AppleAnimator use was also extremely helpful. Some button-like routines adapted from Apple's GenericButton class.
  27.  
  28. Additional skills were obtained by studying Apple's sample files and the Mac OS X bundled widgets, however no actual code was used from these sources other than that mentioned already.
  29.  
  30. ******************************************************************************/
  31.  
  32.  
  33.  
  34. /*******************************/
  35. // GLOBAL VARIABLES & SETTINGS
  36. /*******************************/
  37.  
  38.  
  39. // Sets up a flag to say whether the "What's On?" link should be showing
  40. // (we deliberately don't trigger its hiding on mouseout under certain
  41. // circumstances, setting this variable to false instead to allow us to hide
  42. // it at a later point). If that's not too clear, try checking the functions.
  43. var shouldShowWhatsOn=false;
  44.  
  45. // Sets up a global thingie for the "What's On?" fading animation
  46. var whatsOnAnimator = null;
  47. // and one for the status animation (which is much quicker)
  48. var statusAnimator = null;
  49.  
  50. // sets up a variable to store the XML from checkForUpdate(), also to let
  51. // us know if a request is currently in progress
  52. var externalXmlRequest = null;
  53.  
  54. // variable to store the current version number from Info.plist; only set on
  55. // load, but referred to on multiple occasions
  56. var currentVersion;
  57. // holds the version number of the latest version available; initially taken
  58. // from preferences (if there), it's updated every 2 days by checkForUpdate()
  59. var latestVersion = widget.preferenceForKey("latestversion");
  60. // get the last time we checked for an update
  61. var then = widget.preferenceForKey("lastupdatecheck");
  62.  
  63. // all involved with the button handlers such as theMouseDownHandler //
  64. // this one stores whether the mouse button is down
  65. var theCurrentMouseDownButton = null;
  66. // the button which was clicked
  67. var wasClickedElement = null;
  68. // the element to swap images into
  69. var isElementToChange = null;
  70. // the starting position of the left side of the volume knob
  71. var knobStartLeft = null;
  72. // the cursor's starting x-coordinate; used with the slider
  73. var cursorStartX = null;
  74. // left boundary of the slider
  75. var leftBound = 73;
  76. // right boundary of the slider
  77. var rightBound = 106;
  78.  
  79. // stors the station selected immediately before, and immediately after, showing
  80. // the rear - a function compares these to see if it's changed
  81. var preferenceBefore = null;
  82. var preferenceAfter = null;
  83.  
  84. // used to temporarily store event settings if we use the showStatus() function,
  85. // as we can't pass these directly from it to hideStatus() via setTimeout().
  86. var globalStatusEvent;
  87.  
  88.  
  89.  
  90. /*****************************************************/
  91. // SETUP FUNCTIONS, SAVING AND RETRIEVING PREFERENCES
  92. /*****************************************************/
  93.  
  94.  
  95. // onLoad() is run when the body loads.  It checks to see if there is a
  96. // preference for this widget and if so, applies the preference to the widget.
  97. function onLoad()
  98. {
  99.     if(window.widget)        // always check to make sure that you are running in Dashboard
  100.     {
  101.         // draw the info button, using AppleClasses
  102.         new AppleInfoButton (document.getElementById('info'), 
  103.                          document.getElementById('front'), 
  104.                          'black', 'black', showPrefs);
  105.         // draw the Done button for the back - again, AppleClasses
  106.         new AppleGlassButton(document.getElementById('done'), 'Done', hidePrefs);
  107.         // adds listeners to the "mouse_tracker" span, which holds the logo,
  108.         // so that the "What's On?" link fades in and out on mouseover/out.
  109.         // We set that here instead of hardcoding them into the html as it
  110.         // avoids a (rare) issue where they might not fire properly if the
  111.         // user left Dashboard while the widget was loading.
  112.         document.getElementById("mouse_tracker").addEventListener("mouseover", whatsOnOver, true)
  113.         document.getElementById("mouse_tracker").addEventListener("mouseout", whatsOnOut, true)
  114.         // gets and shows the correct station image
  115.         changeImage();
  116.         // sets the volume slider to the correct position
  117.         restoreVolume();
  118.         // the next section reads the preferences and ensures that the correct
  119.         // option is selected in the popup menu to the rear on load
  120.         var stationPopup = document.getElementById("station");
  121.         // checks to see which option number in the popup was selected last time
  122.         var optionToSelect = widget.preferenceForKey("selection");   
  123.         // The preference was read as a string, not as a number; if we try to
  124.         // take 1 from it(next step) already we hit problems. However,
  125.         // multiplying by 1 turns a numerical string into a number, so we can do
  126.         // math on it. Genius.
  127.         optionToSelectNumber=optionToSelect*1;
  128.         // takes one away; for some reason the numbering system is different
  129.         // for setting the option to that when reading it!
  130.         optionToSelectNumber=optionToSelectNumber-1;
  131.         // we use optionToSelect (the string version) to check that it has
  132.         // length (ie. that a preference exists)
  133.         if (optionToSelect && (optionToSelect.length > 0))
  134.         {
  135.             // and if so, we select the relevant option
  136.             stationPopup.options[optionToSelectNumber].selected = true;
  137.         }
  138.         // if there's no match, default to radio 1, as with all settings
  139.         else    {   stationPopup.options[0].selected = true   }
  140.     }
  141.     
  142.     // calls fillVersion() to fill in the version number on the rear
  143.     fillVersion();
  144.     // check for an update to the widget
  145.     checkForUpdate();
  146.     // if a later version is available
  147.     if (latestVersion>currentVersion)
  148.     {
  149.         // show a status message to alert the user
  150.         showStatus('', 'An update is available! Please check the reverse of the widget.', 10);
  151.     }
  152.  
  153. }
  154.  
  155.  
  156. // called from onLoad() to fill in the author & version information on the rear;
  157. // it grabs them from Info.plist
  158. function fillVersion()
  159. {
  160.     // start a new XMLHttpRequest
  161.     var internalXmlRequest = new XMLHttpRequest();
  162.     // setup the destination of the request (Info.plist)
  163.     internalXmlRequest.open("GET", "Info.plist", false);
  164.     // send nothing
  165.     internalXmlRequest.send(null);
  166.     // get all the "key" tags, set them to an array(?)
  167.     var keys = internalXmlRequest.responseXML.getElementsByTagName("key");
  168.     // set the variable to 0.0, so we have a value just in case this fails
  169.     currentVersion = "0.0";
  170.     // set up a loop, to go through every key tag
  171.     for(i=0; i<keys.length; i++)
  172.     {
  173.         // if we hit a match for CFBundleVersion in the data of the "first
  174.         // child" of any of the keys (XML navigation terminology is rather
  175.         // confusing)
  176.         if("CFBundleVersion" == keys[i].firstChild.data)
  177.         {
  178.             // if we hit CFBundleVersion, we know where the actual version
  179.             // number will be inrelation to it (assuming the .plist is valid)
  180.             // so we can navigate to it - again, confusing-looking, no?
  181.             // Anyway, we set it to the global varible, so we can refer to it
  182.             // in other functions
  183.             currentVersion = keys[i].nextSibling.nextSibling.firstChild.data;
  184.             // break the loop, we got a match so we're done
  185.             break;
  186.         }
  187.     }
  188.     // uses the newly-set variable to fill in the version number, along with
  189.     // the rest of the information, into the span with id="author"
  190.     document.getElementById("author").innerText = "BBC Radio v"+currentVersion+" by Hawkman";
  191. }
  192.  
  193.  
  194. // checks to see what the latest version is. Every two days it looks online at
  195. // my website, where the latest version will be published; in between it uses
  196. // a local cache of the last known value instead.
  197. function checkForUpdate()
  198. {
  199.     // get the current date
  200.     var now = (new Date).getTime();
  201.     // if we haven't checked for an update, or if it's been 2 days. Used to only
  202.     // allow this if an update hadn't been detected previously; however, this
  203.     // doesn't allow for human error. I want to be able to pull new versions if
  204.     // they're buggy and un-trigger the update check!
  205.     if (!then || !latestVersion || (now-then)>172800000)
  206.     {
  207.         // record the time that we're checking, both to preferences and our
  208.         // global variable "then", so that the widget won't check for another
  209.         // 2 days
  210.         widget.setPreferenceForKey(now,"lastupdatecheck");
  211.         // no, it's not time travel; it's two confusingly-named variables!
  212.         then = now;
  213.         // cancel any update request in progress - there damn well shouldn't be
  214.         // one, if everything's working!
  215.         if (externalXmlRequest != null)
  216.         {
  217.             externalXmlRequest.abort();
  218.             externalXmlRequest = null;
  219.         }
  220.         // set up the request
  221.         externalXmlRequest = new XMLHttpRequest();
  222.         // when we've got the data, this calls searchResponseForLatestVersion()
  223.         // to handle it, passing the data to it
  224.         externalXmlRequest.onload = function(e) {searchResponseForLatestVersion(e, externalXmlRequest);}
  225.         // gives the url to query, amongst other things; if you want to see
  226.         // how the other end works, visit that URL in your browser and
  227.         // view the source
  228.         externalXmlRequest.open("GET", "http://www.phantomgorilla.com/update/version.xml", false);
  229.         // make sure we don't get a cached version of the page
  230.         externalXmlRequest.setRequestHeader("Cache-Control", "no-cache");
  231.         // don't send anything
  232.         externalXmlRequest.send(null);
  233.     }
  234.     // now we've got the latest version information, we have to check to see if
  235.     // there's actually an update, or if this version is current.
  236.     // if the latest version available is greater than the current version:
  237.     if (latestVersion>currentVersion)
  238.     {
  239.         // hide the Phantom Gorilla graphic in the lower left
  240.         document.getElementById("phantom").style.display="none"
  241.         // show the "update" graphic instead
  242.         document.getElementById("update").style.display="block"
  243.     }
  244.     // otherwise, do the reverse (in case the update availability has reverted
  245.     // while the widget is open; unlikely, but possible
  246.     else
  247.     {
  248.         document.getElementById("update").style.display="none"
  249.         document.getElementById("phantom").style.display="block"
  250.     }
  251. }
  252.  
  253.  
  254. // searches the nodes in the page for the one that relates to this product, then
  255. // gets the data from it - the latest version number :)
  256. function searchResponseForLatestVersion(e, request)
  257. {
  258.     // set the externalXmlRequest to null, so we know no request is in progress
  259.     externalXmlRequest = null;
  260.     // if there's an answer with XML
  261.     if (request.responseXML)
  262.     {
  263.         // sets up a variable to use
  264.         var theChild; 
  265.         // searches through the nodes in order
  266.         for (theChild=request.responseXML.firstChild; theChild!=null; theChild=theChild.nextSibling)
  267.         {
  268.             // if the node is called "BBCRadio" - that's the one relating to us!
  269.             if (theChild.nodeName == 'BBCRadio')
  270.             {
  271.                 // get its data; this will be the version number. Set that to
  272.                 // latestVersion, which is a global variable.
  273.                 latestVersion = theChild.firstChild.data;
  274.             }
  275.         }
  276.         // stores the latest version information we just got as a preference;
  277.         // even though latestVersion is a global variable, we save this now so
  278.         // that on next load (when it will have cleared) we'll still know what
  279.         // the latest version is without checking online (till 2 days are up)
  280.         widget.setPreferenceForKey(latestVersion, "latestversion");
  281.     }
  282. }
  283.  
  284.  
  285. // Originally changestation(), but it evolved into a behemoth prefs function.
  286. // setPrefs() is called whenever a station is chosen from the popup menu.
  287. // It saves the location of the image and the stream url of the corresponding
  288. // station and then calls functions which change the image and the audio stream
  289. // on the front of the widget.
  290. function setPrefs(elem)
  291. {    
  292.     // defines a variable
  293.     var selectedOption = null;
  294.     // sets selectedOption to the number of the selection
  295.     selectedOption=elem.options[elem.selectedIndex].value;
  296.     // check we're in Dashboard; I'm not as consistent with this as I should be,
  297.     // but we're setting a huge load of preferences here so it's best to be sure
  298.     if(window.widget)
  299.     {
  300.         // save the numbered selection as a preference (so we can set it again
  301.         // quickly on next launch)
  302.         widget.setPreferenceForKey(selectedOption,"selection");
  303.         // find out which option was chosen
  304.         switch(parseInt(elem.options[elem.selectedIndex].value))
  305.         {
  306.             // if the first option (radio 1) was chosen
  307.             case 1:
  308.                 // set the preference for the station logo location
  309.                 widget.setPreferenceForKey("images/r1.gif","logo");
  310.                 // then for service ID (BBC defined, used to get schedule)
  311.                 widget.setPreferenceForKey("49697","ID");
  312.                 // then for "band", used to display extra information (such as
  313.                 // UK or international versions of the station) in top left
  314.                 widget.setPreferenceForKey("","band");
  315.                 // then for the stream url
  316.                 widget.setPreferenceForKey("http://www.bbc.co.uk/radio1/realaudio/media/r1live.rpm","url");
  317.                 // now change the image on the front as we just changed the
  318.                 // location; it needs to be called immediately so that it's
  319.                 // changed by the time we flip back to the front
  320.                 changeImage();
  321.                 // break the loop, as we've found the scenario we wanted
  322.                 break;
  323.             // do the same for the second option (radio 2)
  324.             case 2:
  325.                 widget.setPreferenceForKey("images/r2.gif","logo");
  326.                 widget.setPreferenceForKey("49698","ID");
  327.                 widget.setPreferenceForKey("","band");                
  328.                 widget.setPreferenceForKey("http://www.bbc.co.uk/radio2/realmedia/fmg2.rpm","url");
  329.                 changeImage();
  330.                 break;            
  331.             // option 3, etc etc
  332.             case 3:
  333.                 widget.setPreferenceForKey("images/r3.gif","logo");
  334.                 widget.setPreferenceForKey("49699","ID");
  335.                 widget.setPreferenceForKey("","band");                
  336.                 widget.setPreferenceForKey("http://www.bbc.co.uk/radio3/ram/r3g2.rpm","url");
  337.                 changeImage();
  338.                 break;
  339.             case 4:
  340.                 widget.setPreferenceForKey("images/r4.gif","logo");
  341.                 widget.setPreferenceForKey("49700","ID");
  342.                 widget.setPreferenceForKey("FM","band");                
  343.                 widget.setPreferenceForKey("http://www.bbc.co.uk/radio4/realplayer/media/fmg2.rpm","url");
  344.                 changeImage();
  345.                 break;
  346.             case 5:
  347.                 widget.setPreferenceForKey("images/r4.gif","logo");
  348.                 widget.setPreferenceForKey("49716","ID");
  349.                 widget.setPreferenceForKey("LW","band");                
  350.                 widget.setPreferenceForKey("http://www.bbc.co.uk/radio4/realplayer/media/lwg2.rpm","url");
  351.                 changeImage();
  352.                 break;
  353.             case 6:
  354.                 widget.setPreferenceForKey("images/r5.gif","logo");
  355.                 widget.setPreferenceForKey("49701","ID");
  356.                 widget.setPreferenceForKey("UK","band");                
  357.                 widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream.rpm","url");
  358.                 changeImage();
  359.                 break;
  360.             case 7:
  361.                 widget.setPreferenceForKey("images/r5.gif","logo");
  362.                 widget.setPreferenceForKey("49701","ID");
  363.                 widget.setPreferenceForKey("INTL","band");                
  364.                 widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_int.rpm","url");
  365.                 changeImage();
  366.                 break;
  367.             case 8:
  368.                 widget.setPreferenceForKey("images/6music.gif","logo");
  369.                 widget.setPreferenceForKey("49707","ID");
  370.                 widget.setPreferenceForKey("","band");                
  371.                 widget.setPreferenceForKey("http://www.bbc.co.uk/6music/ram/dsatg2.rpm","url");
  372.                 changeImage();
  373.                 break;
  374.             case 9:
  375.                 widget.setPreferenceForKey("images/bbc7.gif","logo");
  376.                 widget.setPreferenceForKey("18112","ID");
  377.                 widget.setPreferenceForKey("","band");                
  378.                 widget.setPreferenceForKey("http://www.bbc.co.uk/bbc7/realplayer/dsatg2.rpm","url");
  379.                 changeImage();
  380.                 break;
  381.             case 10:
  382.                 widget.setPreferenceForKey("images/1xtra.gif","logo");
  383.                 widget.setPreferenceForKey("49706","ID");
  384.                 widget.setPreferenceForKey("","band");                
  385.                 widget.setPreferenceForKey("http://www.bbc.co.uk/1xtra/realmedia/1xtralive.rpm","url");
  386.                 changeImage();
  387.                 break;
  388.             case 11:
  389.                 widget.setPreferenceForKey("images/5livextra.gif","logo");
  390.                 widget.setPreferenceForKey("49704","ID");
  391.                 widget.setPreferenceForKey("UK","band");                
  392.                 widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_sportsextra.rpm","url");
  393.                 changeImage();
  394.                 break;
  395.             case 12:
  396.                 widget.setPreferenceForKey("images/5livextra.gif","logo");
  397.                 widget.setPreferenceForKey("49704","ID");
  398.                 widget.setPreferenceForKey("INTL","band");                
  399.                 widget.setPreferenceForKey("http://www.bbc.co.uk/fivelive/live/surestream_sportsextra_int.rpm","url");
  400.                 changeImage();
  401.                 break;
  402.             case 13:
  403.                 widget.setPreferenceForKey("images/asian.gif","logo");
  404.                 widget.setPreferenceForKey("2030","ID");
  405.                 widget.setPreferenceForKey("","band");                
  406.                 widget.setPreferenceForKey("http://www.bbc.co.uk/asiannetwork/rams/asiannetwork.rpm","url");
  407.                 changeImage();
  408.                 break;
  409.             case 14:
  410.                 widget.setPreferenceForKey("images/ws.gif","logo");
  411.                 widget.setPreferenceForKey("49702","ID");
  412.                 widget.setPreferenceForKey("","band");                
  413.                 widget.setPreferenceForKey("http://www.bbc.co.uk/worldservice/ram/live_infent.rpm","url");
  414.                 changeImage();
  415.                 break;
  416.             case 15:
  417.                 widget.setPreferenceForKey("images/scotland.gif","logo");
  418.                 widget.setPreferenceForKey("61441","ID");
  419.                 widget.setPreferenceForKey("","band");                
  420.                 widget.setPreferenceForKey("http://www.bbc.co.uk/scotland/radioscotland/media/radioscotland.rpm","url");
  421.                 changeImage();
  422.                 break;
  423.             case 16:
  424.                 widget.setPreferenceForKey("images/wales.gif","logo");
  425.                 widget.setPreferenceForKey("50232","ID");
  426.                 widget.setPreferenceForKey("","band");                
  427.                 widget.setPreferenceForKey("http://www.bbc.co.uk/wales/live/rwg2.rpm","url");
  428.                 changeImage();
  429.                 break;
  430.             case 17:
  431.                 widget.setPreferenceForKey("images/ulster.gif","logo");
  432.                 widget.setPreferenceForKey("49977","ID");
  433.                 widget.setPreferenceForKey("","band");                
  434.                 widget.setPreferenceForKey("http://www.bbc.co.uk/northernireland/realmedia/ru-live.rpm","url");
  435.                 changeImage();
  436.                 break;
  437.             case 18:
  438.                 widget.setPreferenceForKey("images/cymru.gif","logo");
  439.                 widget.setPreferenceForKey("49991","ID");
  440.                 widget.setPreferenceForKey("","band");                
  441.                 widget.setPreferenceForKey("http://www.bbc.co.uk/cymru/live/rc-live.ram","url");
  442.                 changeImage();
  443.                 break;
  444.             case 19:
  445.                 widget.setPreferenceForKey("images/foyle.gif","logo");
  446.                 widget.setPreferenceForKey("50233","ID");
  447.                 widget.setPreferenceForKey("","band");                
  448.                 widget.setPreferenceForKey("http://www.bbc.co.uk/ni/realmedia/rf-live.ram","url");
  449.                 changeImage();
  450.                 break;
  451.             case 20:
  452.                 widget.setPreferenceForKey("images/ng.gif","logo");
  453.                 widget.setPreferenceForKey("49974","ID");
  454.                 widget.setPreferenceForKey("","band");                
  455.                 widget.setPreferenceForKey("http://www.bbc.co.uk/scotland/alba/media/live/radio_ng.ram","url");
  456.                 changeImage();
  457.                 break;
  458.             case 21:
  459.                 widget.setPreferenceForKey("Berkshire","area");
  460.                 widget.setPreferenceForKey("images/local.gif","logo");
  461.                 widget.setPreferenceForKey("52529","ID");
  462.                 widget.setPreferenceForKey("","band");                
  463.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/radioberkshire.ram","url");
  464.                 changeImage();
  465.                 break;
  466.             case 22:
  467.                 widget.setPreferenceForKey("Bristol","area");
  468.                 widget.setPreferenceForKey("images/local.gif","logo");
  469.                 widget.setPreferenceForKey("50226","ID");
  470.                 widget.setPreferenceForKey("","band");                
  471.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/bristol.ram","url");
  472.                 changeImage();
  473.                 break;
  474.             case 23:
  475.                 widget.setPreferenceForKey("Cambridgeshire","area");
  476.                 widget.setPreferenceForKey("images/local.gif","logo");
  477.                 widget.setPreferenceForKey("50225","ID");
  478.                 widget.setPreferenceForKey("","band");                
  479.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cambridgeshire.ram","url");
  480.                 changeImage();
  481.                 break;
  482.             case 24:
  483.                 widget.setPreferenceForKey("Cleveland","area");
  484.                 widget.setPreferenceForKey("images/local.gif","logo");
  485.                 widget.setPreferenceForKey("52020","ID");
  486.                 widget.setPreferenceForKey("","band");                
  487.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cleveland.ram","url");
  488.                 changeImage();
  489.                 break;
  490.             case 25:
  491.                 widget.setPreferenceForKey("Cornwall","area");
  492.                 widget.setPreferenceForKey("images/local.gif","logo");
  493.                 widget.setPreferenceForKey("50994","ID");
  494.                 widget.setPreferenceForKey("","band");                
  495.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cornwall.ram","url");
  496.                 changeImage();
  497.                 break;
  498.             case 26:
  499.                 widget.setPreferenceForKey("Coventry & Warks","area");
  500.                 widget.setPreferenceForKey("images/local.gif","logo");
  501.                 widget.setPreferenceForKey("51251","ID");
  502.                 widget.setPreferenceForKey("","band");                
  503.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/coventryandwarks.ram","url");
  504.                 changeImage();
  505.                 break;
  506.             case 27:
  507.                 widget.setPreferenceForKey("Cumbria","area");
  508.                 widget.setPreferenceForKey("images/local.gif","logo");
  509.                 widget.setPreferenceForKey("50996","ID");
  510.                 widget.setPreferenceForKey("","band");                
  511.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/cumbria.ram","url");
  512.                 changeImage();
  513.                 break;
  514.             case 28:
  515.                 widget.setPreferenceForKey("Derby","area");
  516.                 widget.setPreferenceForKey("images/local.gif","logo");
  517.                 widget.setPreferenceForKey("50739","ID");
  518.                 widget.setPreferenceForKey("","band");                
  519.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/derby.ram","url");
  520.                 changeImage();
  521.                 break;
  522.             case 29:
  523.                 widget.setPreferenceForKey("Devon","area");
  524.                 widget.setPreferenceForKey("images/local.gif","logo");
  525.                 widget.setPreferenceForKey("51250","ID");
  526.                 widget.setPreferenceForKey("","band");                
  527.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/devon.ram","url");
  528.                 changeImage();
  529.                 break;
  530.             case 30:
  531.                 widget.setPreferenceForKey("Essex","area");
  532.                 widget.setPreferenceForKey("images/local.gif","logo");
  533.                 widget.setPreferenceForKey("51249","ID");
  534.                 widget.setPreferenceForKey("","band");                
  535.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/essex.ram","url");
  536.                 changeImage();
  537.                 break;
  538.             case 31:
  539.                 widget.setPreferenceForKey("Gloucestershire","area");
  540.                 widget.setPreferenceForKey("images/local.gif","logo");
  541.                 widget.setPreferenceForKey("52018","ID");
  542.                 widget.setPreferenceForKey("","band");                
  543.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/gloucestershire.ram","url");
  544.                 changeImage();
  545.                 break;
  546.             case 32:
  547.                 widget.setPreferenceForKey("Guernsey","area");
  548.                 widget.setPreferenceForKey("images/local.gif","logo");
  549.                 widget.setPreferenceForKey("50487","ID");
  550.                 widget.setPreferenceForKey("","band");                
  551.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/guernsey.ram","url");
  552.                 changeImage();
  553.                 break;
  554.             case 33:
  555.                 widget.setPreferenceForKey("Hereford & Worcester","area");
  556.                 widget.setPreferenceForKey("images/local.gif","logo");
  557.                 widget.setPreferenceForKey("50483","ID");
  558.                 widget.setPreferenceForKey("","band");                
  559.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/herefordandworcester.ram","url");
  560.                 changeImage();
  561.                 break;
  562.             case 34:
  563.                 widget.setPreferenceForKey("Humberside","area");
  564.                 widget.setPreferenceForKey("images/local.gif","logo");
  565.                 widget.setPreferenceForKey("52532","ID");
  566.                 widget.setPreferenceForKey("","band");                
  567.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/humberside.ram","url");
  568.                 changeImage();
  569.                 break;
  570.             case 35:
  571.                 widget.setPreferenceForKey("Jersey","area");
  572.                 widget.setPreferenceForKey("images/local.gif","logo");
  573.                 widget.setPreferenceForKey("50231","ID");
  574.                 widget.setPreferenceForKey("","band");                
  575.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/jersey.ram","url");
  576.                 changeImage();
  577.                 break;
  578.             case 36:
  579.                 widget.setPreferenceForKey("Kent","area");
  580.                 widget.setPreferenceForKey("images/local.gif","logo");
  581.                 widget.setPreferenceForKey("50737","ID");
  582.                 widget.setPreferenceForKey("","band");                
  583.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/kent.ram","url");
  584.                 changeImage();
  585.                 break;
  586.             case 37:
  587.                 widget.setPreferenceForKey("Lancashire","area");
  588.                 widget.setPreferenceForKey("images/local.gif","logo");
  589.                 widget.setPreferenceForKey("50484","ID");
  590.                 widget.setPreferenceForKey("","band");                
  591.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/lancashire.ram","url");
  592.                 changeImage();
  593.                 break;
  594.             case 38:
  595.                 widget.setPreferenceForKey("Leeds","area");
  596.                 widget.setPreferenceForKey("images/local.gif","logo");
  597.                 widget.setPreferenceForKey("52788","ID");
  598.                 widget.setPreferenceForKey("","band");                
  599.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/leeds.ram","url");
  600.                 changeImage();
  601.                 break;
  602.             case 39:
  603.                 widget.setPreferenceForKey("Leicester","area");
  604.                 widget.setPreferenceForKey("images/local.gif","logo");
  605.                 widget.setPreferenceForKey("51507","ID");
  606.                 widget.setPreferenceForKey("","band");                
  607.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/leicester.ram","url");
  608.                 changeImage();
  609.                 break;
  610.             case 40:
  611.                 widget.setPreferenceForKey("Lincolnshire","area");
  612.                 widget.setPreferenceForKey("images/local.gif","logo");
  613.                 widget.setPreferenceForKey("52019","ID");
  614.                 widget.setPreferenceForKey("","band");                
  615.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/lincolnshire.ram","url");
  616.                 changeImage();
  617.                 break;
  618.             case 41:
  619.                 widget.setPreferenceForKey("London","area");
  620.                 widget.setPreferenceForKey("images/local.gif","logo");
  621.                 widget.setPreferenceForKey("52273","ID");
  622.                 widget.setPreferenceForKey("","band");                
  623.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/london.ram","url");
  624.                 changeImage();
  625.                 break;
  626.             case 42:
  627.                 widget.setPreferenceForKey("Manchester","area");
  628.                 widget.setPreferenceForKey("images/local.gif","logo");
  629.                 widget.setPreferenceForKey("50228","ID");
  630.                 widget.setPreferenceForKey("","band");                
  631.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/manchester.ram","url");
  632.                 changeImage();
  633.                 break;
  634.             case 43:
  635.                 widget.setPreferenceForKey("Merseyside","area");
  636.                 widget.setPreferenceForKey("images/local.gif","logo");
  637.                 widget.setPreferenceForKey("50740","ID");
  638.                 widget.setPreferenceForKey("","band");                
  639.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/merseyside.ram","url");
  640.                 changeImage();
  641.                 break;
  642.             case 44:
  643.                 widget.setPreferenceForKey("Newcastle","area");
  644.                 widget.setPreferenceForKey("images/local.gif","logo");
  645.                 widget.setPreferenceForKey("51764","ID");
  646.                 widget.setPreferenceForKey("","band");                
  647.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/newcastle.ram","url");
  648.                 changeImage();
  649.                 break;
  650.             case 45:
  651.                 widget.setPreferenceForKey("Norfolk","area");
  652.                 widget.setPreferenceForKey("images/local.gif","logo");
  653.                 widget.setPreferenceForKey("51505","ID");
  654.                 widget.setPreferenceForKey("","band");                
  655.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/norfolk.ram","url");
  656.                 changeImage();
  657.                 break;
  658.             case 46:
  659.                 widget.setPreferenceForKey("Northampton","area");
  660.                 widget.setPreferenceForKey("images/local.gif","logo");
  661.                 widget.setPreferenceForKey("53041","ID");
  662.                 widget.setPreferenceForKey("","band");                
  663.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/northampton.ram","url");
  664.                 changeImage();
  665.                 break;
  666.             case 47:
  667.                 widget.setPreferenceForKey("Nottingham","area");
  668.                 widget.setPreferenceForKey("images/local.gif","logo");
  669.                 widget.setPreferenceForKey("50995","ID");
  670.                 widget.setPreferenceForKey("","band");                
  671.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/nottingham.ram","url");
  672.                 changeImage();
  673.                 break;
  674.             case 48:
  675.                 widget.setPreferenceForKey("Oxford","area");
  676.                 widget.setPreferenceForKey("images/local.gif","logo");
  677.                 widget.setPreferenceForKey("52017","ID");
  678.                 widget.setPreferenceForKey("","band");                
  679.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/radiooxford.ram","url");
  680.                 changeImage();
  681.                 break;
  682.             case 49:
  683.                 widget.setPreferenceForKey("Sheffield","area");
  684.                 widget.setPreferenceForKey("images/local.gif","logo");
  685.                 widget.setPreferenceForKey("53044","ID");
  686.                 widget.setPreferenceForKey("","band");                
  687.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/sheffield.ram","url");
  688.                 changeImage();
  689.                 break;
  690.             case 50:
  691.                 widget.setPreferenceForKey("Shropshire","area");
  692.                 widget.setPreferenceForKey("images/local.gif","logo");
  693.                 widget.setPreferenceForKey("51763","ID");
  694.                 widget.setPreferenceForKey("","band");                
  695.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/shropshire.ram","url");
  696.                 changeImage();
  697.                 break;
  698.             case 51:
  699.                 widget.setPreferenceForKey("Solent","area");
  700.                 widget.setPreferenceForKey("images/local.gif","logo");
  701.                 widget.setPreferenceForKey("51506","ID");
  702.                 widget.setPreferenceForKey("","band");                
  703.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/solent.ram","url");
  704.                 changeImage();
  705.                 break;
  706.             case 52:
  707.                 widget.setPreferenceForKey("Somerset","area");
  708.                 widget.setPreferenceForKey("images/local.gif","logo");
  709.                 widget.setPreferenceForKey("40961","ID");
  710.                 widget.setPreferenceForKey("","band");                
  711.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/somerset.ram","url");
  712.                 changeImage();
  713.                 break;
  714.             case 53:
  715.                 widget.setPreferenceForKey("Southern Counties","area");
  716.                 widget.setPreferenceForKey("images/local.gif","logo");
  717.                 widget.setPreferenceForKey("52768","ID");
  718.                 widget.setPreferenceForKey("","band");                
  719.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/southerncounties.ram","url");
  720.                 changeImage();
  721.                 break;
  722.             case 54:
  723.                 widget.setPreferenceForKey("Stoke","area");
  724.                 widget.setPreferenceForKey("images/local.gif","logo");
  725.                 widget.setPreferenceForKey("52275","ID");
  726.                 widget.setPreferenceForKey("","band");                
  727.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/stoke.ram","url");
  728.                 changeImage();
  729.                 break;
  730.             case 55:
  731.                 widget.setPreferenceForKey("Suffolk","area");
  732.                 widget.setPreferenceForKey("images/local.gif","logo");
  733.                 widget.setPreferenceForKey("51761","ID");
  734.                 widget.setPreferenceForKey("","band");                
  735.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/suffolk.ram","url");
  736.                 changeImage();
  737.                 break;
  738.             case 56:
  739.                 widget.setPreferenceForKey("Swindon","area");
  740.                 widget.setPreferenceForKey("images/local.gif","logo");
  741.                 widget.setPreferenceForKey("51762","ID");
  742.                 widget.setPreferenceForKey("","band");                
  743.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/swindon.ram","url");
  744.                 changeImage();
  745.                 break;
  746.             case 57:
  747.                 widget.setPreferenceForKey("Three Counties","area");
  748.                 widget.setPreferenceForKey("images/local.gif","logo");
  749.                 widget.setPreferenceForKey("50993","ID");
  750.                 widget.setPreferenceForKey("","band");                
  751.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/threecounties.ram","url");
  752.                 changeImage();
  753.                 break;
  754.             case 58:
  755.                 widget.setPreferenceForKey("Wiltshire","area");
  756.                 widget.setPreferenceForKey("images/local.gif","logo");
  757.                 widget.setPreferenceForKey("52274","ID");
  758.                 widget.setPreferenceForKey("","band");                
  759.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/wiltshire.ram","url");
  760.                 changeImage();
  761.                 break;
  762.             case 59:
  763.                 widget.setPreferenceForKey("WM","area");
  764.                 widget.setPreferenceForKey("images/local.gif","logo");
  765.                 widget.setPreferenceForKey("50227","ID");
  766.                 widget.setPreferenceForKey("","band");                
  767.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/wm.ram","url");
  768.                 changeImage();
  769.                 break;
  770.             case 60:
  771.                 widget.setPreferenceForKey("York","area");
  772.                 widget.setPreferenceForKey("images/local.gif","logo");
  773.                 widget.setPreferenceForKey("52276","ID");
  774.                 widget.setPreferenceForKey("","band");                
  775.                 widget.setPreferenceForKey("http://www.bbc.co.uk/england/realmedia/live/localradio/york.ram","url");
  776.                 changeImage();
  777.                 break;
  778.         }
  779.     }
  780.  
  781.  
  782. // changeImage() is called on loading and after preference changes.
  783. // It checks which images should be shown and changes the html to show it.
  784. // Also adds any additional info such as local radio area over the image,
  785. // if appropriate.
  786. function changeImage()
  787. {
  788.     // read preferences and set variable for the elements
  789.     var logo = widget.preferenceForKey("logo");
  790.     var localName = widget.preferenceForKey("area");
  791.     var stationBand = widget.preferenceForKey("band");
  792.     var logoImage = document.getElementById("logo");
  793.     var localContainer = document.getElementById("local_name_container");
  794.     var bandContainer = document.getElementById("band_container");
  795.     // not relevant after the first time this version is run, but we have an
  796.     // all-new efficient way of reselecting the appropriate station from the
  797.     // popup based on preferences, which defaults to radio 1 if there's no pref
  798.     // (first run). Checking for that preference here helps us keep everything
  799.     // in sync, without having to include a lot of old, messy code as well.
  800.     var optionToSelect = widget.preferenceForKey("selection");
  801.     // check for valid preference
  802.     if (optionToSelect && (optionToSelect.length>0) && logo && (logo.length>0))
  803.     // set the source of the image to the path which we just read from
  804.     // preferences
  805.     {   logoImage.src=logo   }
  806.     // default to r1, as with all settings, if nothing's saved, or if there's
  807.     // no "optionToSelect" (first run, or first run of new version)
  808.     else
  809.     {   logoImage.src="images/r1.gif"   };
  810.     // if it's the local radio station image
  811.     if (logo=="images/local.gif")
  812.     {   
  813.         // sets the text inside it to match the name of the station (set during
  814.         // setPrefs())
  815.         localContainer.innerText = localName;
  816.         // show the div which will contain the name of the local station
  817.         localContainer.style.display="block";  
  818.         // and hide the LW/FM/UK/INTL indicator as it's redundant here
  819.         bandContainer.style.display="none";
  820.     }
  821.     // otherwise, if there are multiple station bands/variants
  822.     else if (stationBand && stationBand.length > 0)
  823.     {
  824.         // add the appropriate band/variant info
  825.         bandContainer.innerText = stationBand;
  826.         // hide the local info, it's redundant
  827.         localContainer.style.display="none";
  828.         // show the band container
  829.         bandContainer.style.display="block";
  830.     }
  831. else
  832.     {
  833.         // turn off both if neither needed
  834.         localContainer.style.display="none";
  835.         bandContainer.style.display="none";
  836.     }
  837. }
  838.  
  839.  
  840. // changeStream() - formerly changeStreamUrl() - is called when the play button
  841. // is pressed, or after preferences are changed. It checks which stream should
  842. // be playing, and then composes some html which embeds the real player plugin
  843. // in the widget to stream the audio. Then the html is pasted into the widget.
  844. function changeStream()
  845. {
  846.     // read prefs and locate the span to paste into
  847.     // (html is pasted into the document to place the RealPlayer plugin with the
  848.     // correct stream url)
  849.     var playerContainer = document.getElementById("radio");
  850.     var station = widget.preferenceForKey("url");
  851.  
  852.     // as before, including this pref check ensures predictable behaviour after
  853.     // upgrading from an earlier version
  854.     var optionToSelect = widget.preferenceForKey("selection");   
  855.  
  856.     // check that prefs are valid
  857.     if (station && (station.length > 0) && optionToSelect && (optionToSelect.length > 0))
  858.     {
  859.         // this section composes the html in the form an embed tag for
  860.         // RealPlayer, which is provided with a custom source url which we read
  861.         // from preferences earlier
  862.         var html = '<embed src="'+station+'" type="audio/x-pn-realaudio-plugin" autostart="true" height="0" width="0" console="one" nojava="true" id="rp" scriptcallbacks="OnPlayStateChange" />';
  863.     }
  864.     // if there aren't valid preferences, we deault to radio 1 again
  865.     else
  866.     {
  867.         // same html as before, but this time the src url is fixed at radio 1
  868.         var html = '<embed src="http://www.bbc.co.uk/radio1/realaudio/media/r1live.rpm" type="audio/x-pn-realaudio-plugin" autostart="true" height="0" width="0" console="one" nojava="true" id="rp" scriptcallbacks="OnPlayStateChange" />';
  869.     }
  870.     // change the inner html of the radio span to the html we just composed
  871.     playerContainer.innerHTML = html;
  872.     // set the player to the volume the slider is showing
  873.     changeVolume();
  874.     // ensures that RealPlayer plugin suppresses any errors, avoiding ugly
  875.     // dialogue boxes
  876.     document.getElementById("rp").SetWantErrors(true);
  877. }
  878.  
  879.  
  880.  
  881. /*****************************************/
  882. // HANDLING PLAYER INTERFACE MOUSE EVENTS
  883. /*****************************************/
  884.  
  885.  
  886. // Called when the mouse goes over the div ID "whats_on", to fade the link in
  887. function whatsOnOver(e)
  888. {
  889.     // Set our flag to true - the link should be showing
  890.     shouldShowWhatsOn=true;
  891.     // check that the mouse isn't down (ie, that the user isn't playing with
  892.     // buttons), and that the progress indicator isn't runnng. Showing the
  893.     // "What's On?" link in either case would be inappropriate.
  894.     // Because shouldShowWhatsOn=true is outside of this "if" clause, we can
  895.     // check to see if it's true after the user releases the mouse / the
  896.     // indicator stops, and if appropriate fade the link in.
  897.     if (theCurrentMouseDownButton==null && progressIndicatorTimer==null)
  898.     {
  899.         // This next section is all about the new(ish) AppleAnimator
  900.         // See the developer documentation, it has better explanations than I
  901.         // can give here
  902.         // value to start at - in this case, 0, as we're starting from 0 opacity
  903.         var from = 0;
  904.         // if the animator is running, in whichever direction
  905.         if(null != whatsOnAnimator)
  906.         {
  907.             // stops it
  908.             whatsOnAnimator.stop();
  909.             // sets from as the current value instead
  910.             from = whatsOnAnimator.animations[0].now;
  911.         }
  912.         // if the shift key is down, the multiplier is 10, if not, 1
  913.         var multiplier = e.shiftKey ? 10 : 1;
  914.         // upshot of this is, increasing values 0 -> 1 get passed to whatsOnHandler
  915.         whatsOnAnimator = new AppleAnimator(500*multiplier, 13, from, 1, whatsOnHandler);
  916.         // starts it all off!
  917.         whatsOnAnimator.start();
  918.     }
  919. }
  920.  
  921.  
  922. // Does the opposite - fades the link out on mouseout
  923. function whatsOnOut(e)
  924. {    
  925.     // we shouldn't be showing the link, set the flag accordingly
  926.     shouldShowWhatsOn=false;   
  927.     // if the mouse isn't down having clicked on a button (this is set in the
  928.     // button handlers); this link is governed by those handlers too, so
  929.     // basically, if you click and drag away the link won't fade. Sweet huh?
  930.     // It'll fade after you let go, though, because we check to see if
  931.     // shouldShowWhatsOn is false (notice how that's outside of this "if"? ;)
  932.     if (theCurrentMouseDownButton==null)
  933.     {
  934.         // again, setup; this time starting at 1 (opaque)
  935.         var from = 1;
  936.         // if it's running already, in either direction
  937.         if(null != whatsOnAnimator)
  938.         {
  939.             // stop it
  940.             whatsOnAnimator.stop();
  941.             // start at current value instead
  942.             from = whatsOnAnimator.animations[0].now;
  943.         }
  944.         // if the shift key is down, the multiplier is 10, if not, 1
  945.         var multiplier = e.shiftKey ? 10 : 1;
  946.         // pass decreasing values to the whatsOnHandler
  947.         whatsOnAnimator = new AppleAnimator(500*multiplier, 13, from, 0, whatsOnHandler);
  948.         // go!
  949.         whatsOnAnimator.start();
  950.     }
  951. }
  952.  
  953.  
  954. // Here's the magic - apply the values passed to whatsOnHandler as
  955. // opacity changes to the link
  956. function whatsOnHandler(animation, current, from, to)
  957. {
  958.     arrow.style.opacity = current;
  959.     whats_on_text.style.opacity = current;
  960.     // this makes the bezel fade in to a maximum of 0.65 opacity, but still
  961.     // taking the same time overall (so it's at a slower rate)
  962.     bezel.style.opacity = current*0.65;
  963. }
  964.  
  965.  
  966. // called when the mouse clicks on certain objects.
  967. // we get passed the reference of the object on which the mouse went down, and
  968. // whether it was the text link or the arrow (different behavoiur, if the arrow
  969. // was clicked we make it glow slightly)
  970. function theMouseDownHandler(event, div, clickedElement)
  971. {
  972.     // loads each image we could use into a cariable, so that they're cached
  973.     // and won't flicker when we swap them in
  974.     var img = new Image;
  975.     img.src = "images/arrow_clicked.png";
  976.     img.src = "images/arrow.png";
  977.     img.src = "images/play_clicked.png";
  978.     img.src = "images/play.png";
  979.     img.src = "images/pause_clicked.png";
  980.     img.src = "images/pause.png";
  981.     img.src = "images/knob_clicked.png";
  982.     img.src = "images/knob.png";
  983.  
  984.     // passes the clickedElement (arrow/text) to the global variable
  985.     // wasClickedElement so the other functions can use it
  986.     wasClickedElement=clickedElement;
  987.  
  988.     // if we clicked on the play button
  989.     if (wasClickedElement=="play")
  990.     {
  991.         // change to the clicked play button image
  992.         document.getElementById("play").src="images/play_clicked.png";
  993.     }
  994.     // if we clicked on the pause button
  995.     if (wasClickedElement=="pause")
  996.     {
  997.         // change to the clicked play button image
  998.         document.getElementById("pause").src="images/pause_clicked.png";
  999.     }
  1000.     // if we clicked on the arrow button
  1001.     else if (wasClickedElement=="arrow")
  1002.     {
  1003.         // change to the clicked arrow image
  1004.         document.getElementById("arrow").src="images/arrow_clicked.png";
  1005.     }
  1006.     // if it's the volume slider/knob that was clicked
  1007.     else if (wasClickedElement=="knob" || wasClickedElement=="slider")
  1008.     {
  1009.         // change to the clicked knob image
  1010.         document.getElementById("knob").src="images/knob_clicked.png";
  1011.         // Save starting positions of cursor and element.
  1012.         cursorStartX = event.clientX;
  1013.         knobStartLeft = parseInt(document.getElementById("knob").style.left, 10)
  1014.     }
  1015.     // if it's the volume slider, move the knob to point of click
  1016.     if (wasClickedElement=="slider")
  1017.     {
  1018.         // allowing for the size of the knob (we want it centred over our click,
  1019.         // but we position it using the left hand side) move it all the way to
  1020.         // the left if user clicked on the left boundary (global, set at start)
  1021.         if ((cursorStartX-6) < leftBound)
  1022.         {
  1023.             var skipKnobToHere = leftBound;
  1024.         }
  1025.         // same as above, but for right boundary (also set at start)
  1026.         else if ((cursorStartX-6) > rightBound)
  1027.         {
  1028.             var skipKnobToHere = rightBound;
  1029.         }
  1030.         // otherwise, skip the knob to where we clicked; again, allowing for the
  1031.         // size & the fact that we want to centre the knob over our click
  1032.         else
  1033.         {
  1034.             var skipKnobToHere = (cursorStartX-6);
  1035.         }
  1036.         // do the actual moving via setting the CSS property
  1037.         document.getElementById("knob").style.left = skipKnobToHere + "px";
  1038.         // reset the knob's starting position to the new position, in case
  1039.         // we do some dragging after the click
  1040.         // Are you impressed that this is allowed? ;) I'm quite proud of this!
  1041.         knobStartLeft = parseInt(document.getElementById("knob").style.left, 10)
  1042.         // calls the function to change the volume, based on the new position
  1043.         changeVolume();
  1044.     }
  1045.     // if we didn't click on the text
  1046.     if (wasClickedElement!="text")
  1047.     {
  1048.         // force the cursor to an arrow, in case we mouseover the text section
  1049.         // of "what's on?" - the cursor turning into a hand during drag would
  1050.         // look funny
  1051.         document.body.style.cursor = "default";
  1052.     }
  1053.     // add listeners, to trigger handlers if the mouse does anything
  1054.     document.addEventListener("mousemove", theMouseMoveHandler, true);
  1055.     document.addEventListener("mouseup", theMouseUpHandler, true);
  1056.     div.addEventListener("mouseover", theMouseOverHandler, true);
  1057.     div.addEventListener("mouseout", theMouseOutHandler, true);
  1058.     // set it up so we know globally that the mouse is down, and where
  1059.     div.arrowInside = true;
  1060.     theCurrentMouseDownButton = div;
  1061.     // I dunno, but Apple do this in their button handlers (from which all these
  1062.     // are derived)
  1063.     event.stopPropagation();
  1064.     event.preventDefault();
  1065. }
  1066.  
  1067.  
  1068. // when the mouse moves after mousedown, this is called
  1069. function theMouseMoveHandler (event)
  1070. {
  1071.     // if we clicked on the knob or the slider; ie, we're in a drag
  1072.     if (wasClickedElement=="knob" || wasClickedElement=="slider")
  1073.     {
  1074.         // calculate the knob position based on its start point and how much
  1075.         // the cursor has moved
  1076.         var newPosition = (knobStartLeft + event.clientX - cursorStartX);
  1077.         // if that's within the slider boundaries, move there
  1078.         if (newPosition > leftBound && newPosition < rightBound)
  1079.         {
  1080.             document.getElementById("knob").style.left = newPosition + "px";
  1081.         }
  1082.         // if it's beyond the left boundary (set globally, at start)
  1083.         else if (newPosition < leftBound)
  1084.         {
  1085.             // just skip to the left boundary instead
  1086.             document.getElementById("knob").style.left = leftBound + "px";
  1087.         }
  1088.         // if it's beyond the right boundary (again, set globally, at start)
  1089.         else if (newPosition > rightBound)
  1090.         {
  1091.             // skip to the right boundary
  1092.             document.getElementById("knob").style.left = rightBound + "px";
  1093.         }
  1094.         // calls changeVolume() to update the volume based on these moves
  1095.         changeVolume();
  1096.     }
  1097.     // like I said, Apple use these
  1098.     event.stopPropagation();
  1099.     event.preventDefault();
  1100. }
  1101.  
  1102.  
  1103. // this is called if the mouse goes back over the element - obviously, we must
  1104. // already have had a mouseout event for this to be being called!
  1105. function theMouseOverHandler (event)
  1106. {
  1107.     // if the mouse button's still down, and the original click was on the arrow
  1108.     // or the button (not the knob or slider, we don't let the knob change
  1109.     // back from blue till the mouse button is released, so there's no point
  1110.     // in calling them here again)
  1111.     if (theCurrentMouseDownButton && wasClickedElement=="arrow")
  1112.     {
  1113.         // change to the clicked images
  1114.         document.getElementById("arrow").src="images/arrow_clicked.png";
  1115.     }
  1116.     else if (theCurrentMouseDownButton && wasClickedElement=="play")
  1117.     {
  1118.         // change to the clicked images
  1119.         document.getElementById("play").src="images/play_clicked.png";
  1120.     }
  1121.     else if (theCurrentMouseDownButton && wasClickedElement=="pause")
  1122.     {
  1123.         // change to the clicked images
  1124.         document.getElementById("pause").src="images/pause_clicked.png";
  1125.     }
  1126.     // set variable to show the mouse is currently over the element clicked on
  1127.     theCurrentMouseDownButton.arrowInside = true;
  1128.     // getting bored of saying "I don't know"
  1129.     event.stopPropagation();
  1130.     event.preventDefault();
  1131. }
  1132.  
  1133.  
  1134. // called if the mouse moves away from the element clicked on
  1135. function theMouseOutHandler (event)
  1136. {
  1137.     // if the button's still down, and the click was on the arrow
  1138.     if (theCurrentMouseDownButton && wasClickedElement=="arrow")
  1139.     {
  1140.         // switch to the unclicked image, as the mouse isn't over it
  1141.         document.getElementById("arrow").src="images/arrow.png";
  1142.     }
  1143.     // same for the play button
  1144.     else if (theCurrentMouseDownButton && wasClickedElement=="play")
  1145.     {
  1146.         // switch to the unclicked image, as the mouse isn't over it
  1147.         document.getElementById("play").src="images/play.png";
  1148.     }
  1149.     // and the pause button
  1150.     else if (theCurrentMouseDownButton && wasClickedElement=="pause")
  1151.     {
  1152.         // switch to the unclicked image, as the mouse isn't over it
  1153.         document.getElementById("pause").src="images/pause.png";
  1154.     }
  1155.     // set variable to indicate the mouse is outside the element clicked on
  1156.     theCurrentMouseDownButton.arrowInside = false;
  1157.     // je ne sais pas
  1158.     event.stopPropagation();
  1159.     event.preventDefault();
  1160. }
  1161.  
  1162.  
  1163. // when the button is released, this is called - the business part of all this
  1164. function theMouseUpHandler (event)
  1165. {
  1166.     // here we chance any image elements back to the unclicked form
  1167.     if (theCurrentMouseDownButton && wasClickedElement=="arrow")
  1168.     {
  1169.         // put the unclicked images back, the mouse is up!
  1170.         document.getElementById("arrow").src="images/arrow.png";
  1171.     }
  1172.     else if (theCurrentMouseDownButton && wasClickedElement=="play")
  1173.     {
  1174.         // put the unclicked images back, the mouse is up!
  1175.         document.getElementById("play").src="images/play.png";
  1176.     }
  1177.     else if (theCurrentMouseDownButton && wasClickedElement=="pause")
  1178.     {
  1179.         // put the unclicked images back, the mouse is up!
  1180.         document.getElementById("pause").src="images/pause.png";
  1181.     }
  1182.     else if (theCurrentMouseDownButton && (wasClickedElement=="knob" || wasClickedElement=="slider"))
  1183.     {
  1184.         // put the unclicked images back, the mouse is up!
  1185.         document.getElementById("knob").src="images/knob.png";
  1186.     }
  1187.     // callback to the client, clean up
  1188.     document.removeEventListener("mousemove", theMouseMoveHandler, true);
  1189.     document.removeEventListener("mouseup", theMouseUpHandler, true);
  1190.     if (theCurrentMouseDownButton)
  1191.     {
  1192.         theCurrentMouseDownButton.removeEventListener("mouseover", theMouseOverHandler, true);
  1193.         theCurrentMouseDownButton.removeEventListener("mouseout", theMouseOutHandler, true);
  1194.     }
  1195.     // Quintus est puer Romanus
  1196.     event.stopPropagation();
  1197.     event.preventDefault();
  1198.     // IF the button was depressed over the text/arrow, and IF it's currently
  1199.     // still over it, call a function
  1200.     if (theCurrentMouseDownButton &&
  1201.         theCurrentMouseDownButton.arrowInside)
  1202.     {
  1203.     if (wasClickedElement=="arrow" || wasClickedElement=="text")
  1204.         {
  1205.            // show the what's on page
  1206.             openWhatsOnPage();
  1207.         }
  1208.     // if it was the play button instead, call another function
  1209.     else if (wasClickedElement=="play")
  1210.         {
  1211.             // this places the plugin (unless audio is already playing)
  1212.             playClicked(event);
  1213.         }
  1214.     // if it was the pause button instead, call another function
  1215.     else if (wasClickedElement=="pause")
  1216.         {
  1217.             // this removes the plugin
  1218.             pauseClicked();
  1219.         }
  1220.     }
  1221.     // reset the variable, as the button's been released
  1222.     theCurrentMouseDownButton = null;
  1223.     // restore the cursor to normal behaviour
  1224.     document.body.style.cursor = "auto";
  1225.     // Called whether we visit the link or not, if we clicked on it.
  1226.     // Check to see if the link should be visible. We suppressed its auto-hide
  1227.     // while the mouse was still down (remember that check to see the state
  1228.     // of theCurrentMouseDownButton in the whatsOnOut() function?) - and just
  1229.     // set this flag as false to remind us to do it later.
  1230.     // So now we need to hide it again if it shouldn't be showing.
  1231.     if (shouldShowWhatsOn==false && (wasClickedElement=="arrow" || wasClickedElement=="text"))
  1232.     {
  1233.        // calls the fade out
  1234.        whatsOnOut(event);
  1235.     }
  1236.     // Likewise, if we didn't click on "what's on" and in the meantime we've
  1237.     // moused over it, fade the link in
  1238.     if (shouldShowWhatsOn==true && (wasClickedElement=="play" || wasClickedElement=="knob" || wasClickedElement=="slider"))
  1239.     {
  1240.        // calls the fade in
  1241.        whatsOnOver(event);
  1242.     }
  1243. }
  1244.  
  1245.  
  1246. // openWhatsOnPage() tells the widget to open the appropriate schedule in
  1247. // a browser window
  1248. function openWhatsOnPage()
  1249. {
  1250.     // read the preference for the "service_id", the station identifier
  1251.     // that goes in the link to give us the correct schedule.
  1252.     // BBC-defined, goodness knows on what basis
  1253.     var serviceId = widget.preferenceForKey("ID");
  1254.     // check for a pref
  1255.     if (serviceId && (serviceId.length > 0))
  1256.     {
  1257.         // open the link. Notice the serviceId variable getting spliced in there
  1258.         widget.openURL('http://www.bbc.co.uk/cgi-perl/whatson/search/daylist.cgi?service_id='+serviceId+'&DAY=Today');
  1259.     }
  1260.     else
  1261.     {
  1262.         // defaults to radio 1, as do all prefs
  1263.         widget.openURL('http://www.bbc.co.uk/cgi-perl/whatson/search/daylist.cgi?service_id=49697&DAY=Today');
  1264.     }
  1265. }
  1266.  
  1267.  
  1268. // called when the play button is clicked (from theMouseUphandler), places the
  1269. // plugin
  1270. function playClicked(e)
  1271. {
  1272.     // start the progress indicator running (so long as it's 
  1273.     // currently stopped, otherwise we'd get it going double
  1274.     // speed!)
  1275.     if (progressIndicatorTimer==null)
  1276.     {
  1277.         startProgressIndicator('large');
  1278.     }
  1279.     // hide the play button, show the pause button
  1280.     document.getElementById("play").style.display="none";
  1281.     document.getElementById("pause").style.display="block";
  1282.     // pinched this idea from the BBC ListenLive widget; hope Duncan Ponting
  1283.     // doesn't mind. It's a great trick!
  1284.     // sniff for the Real Player plugin; first set up a flag as false
  1285.     var pluginHere = false;
  1286.     // loop through all the plugins
  1287.     for (var i=0;i < navigator.plugins.length; i++)
  1288.     {
  1289.         // if the current one's called "RealPlayer Plugin"
  1290.         if (navigator.plugins[i].name.indexOf('RealPlayer')!=(-1))
  1291.         {
  1292.             //  set our flag to true
  1293.             pluginHere = true;
  1294.             // break the loop, we've found it
  1295.             break;
  1296.         }
  1297.     }
  1298.     // if the plugin's not installed
  1299.     if (!pluginHere)
  1300.     {
  1301.         // show a status message, with link
  1302.         showStatus(e,'<a href=javascript:widget.openURL("http://www.real.com/R/RC.021705realhome_1_2_2_1_1_3.ecomm...R/realguide.real.com/r/https_.html?url=order.real.com%2fpt%2forder.html%3fppath%3dcpmacpl060204a%26country%3dUS%26language%3dEN%26opage%3drealhome%26src%3d021705realhome_1_2_2_1_1_3")>RealPlayer plugin not found. Click text to download. (Widget will need to be restarted)</a>',15);
  1303.         // pretend pause is clicked, so we stop the indicator and reset the
  1304.         // button images
  1305.         pauseClicked();
  1306.     }
  1307.     // if the plugin's installed
  1308.     // use xmlhttprequest to test for internet connection - if we can complete
  1309.     // a connection to the BBC radio player page we proceed and place the player
  1310.     // (if we place it without a connection we could get a crash later!)
  1311.     else
  1312.     {
  1313.         // set up the request
  1314.         var xmlhttp = new XMLHttpRequest();
  1315.         // location et al
  1316.         xmlhttp.open("HEAD", "http://www.bbc.co.uk/radio/index.shtml",true);
  1317.         // every time the state of the request changes
  1318.         // run the following code
  1319.         xmlhttp.onreadystatechange=function()
  1320.         {
  1321.             // if the connection request is complete
  1322.             if (xmlhttp.readyState==4)
  1323.             {
  1324.                 // and if the request was successful
  1325.                 if (xmlhttp.status==200)
  1326.                 {
  1327.                     // place an instance of the plugin, change the volume,
  1328.                     // change the play button to a pause button
  1329.                     changeStream();
  1330.                 }
  1331.                 // if unsuccessful
  1332.                 else
  1333.                 {
  1334.                     // calls a function to show a "no connection" warning
  1335.                     showStatus(e,"You don't appear to be connected to the internet. Please check your connection and try again.", 10);
  1336.                     // pretend pause is clicked, so we stop the indicator and
  1337.                     // reset the button images
  1338.                     pauseClicked();
  1339.                 }
  1340.             }
  1341.         }
  1342.         // send nothing
  1343.         xmlhttp.send(null);
  1344.     }
  1345. }
  1346.  
  1347.  
  1348. // Called when pause is clicked, to remove the plugin and stop audio
  1349. function pauseClicked()
  1350. {
  1351.     // stop the progress indicator, in case it was running
  1352.     stopProgressIndicator();
  1353.     // resets the inner html of the span holding the plugin
  1354.     document.getElementById("radio").innerHTML="";
  1355.     // hide the pause button, shaow the play button
  1356.     document.getElementById("pause").style.display = "none";
  1357.     document.getElementById("play").style.display = "block";
  1358. }
  1359.  
  1360.  
  1361. // show a status message
  1362. function showStatus(e, message, duration)
  1363. {
  1364.     // pass the event to a global variable (we can't pass it on to hideStatus()
  1365.     // because we call it via setTimeout(), which doesn't support that, so we
  1366.     // have to make it global)
  1367.     globalStatusEvent=e;
  1368.     // set the inner html to the message we got passed
  1369.     document.getElementById("status").innerHTML=message;
  1370.     // show the status div (although it's transparent at the moment)
  1371.     document.getElementById("status").style.display="block";
  1372.     // hide all the "What's On?" stuff, in case it shows
  1373.     document.getElementById("whats_on_text").style.display="none";
  1374.     document.getElementById("bezel").style.display="none";
  1375.     document.getElementById("arrow").style.display="none";
  1376.     // setting up an AppleAnimator, to fade in the status message (speedily)
  1377.     // starting from 0 opacity
  1378.     var from=0;
  1379.     // if it's running
  1380.     if(null != statusAnimator)
  1381.     {
  1382.         // stop
  1383.         statusAnimator.stop();
  1384.         // start from current value
  1385.         from = statusAnimator.animations[0].now;
  1386.     }
  1387.     // if the shift key is down, the multiplier is 10, if not, 1
  1388.     var multiplier = e.shiftKey ? 10 : 1;
  1389.     // upshot of this is, increasing values get passed to statusFadeHandler
  1390.     statusAnimator = new AppleAnimator(500*multiplier, 13, from, 1, statusFadeHandler);
  1391.     // starts it all off!
  1392.     statusAnimator.start();
  1393.     // call hideStatus after the amount of time we specified, to give plenty of
  1394.     // time to read it (we multiply by 1000 as 'duration' is specified in
  1395.     // seconds, but setTimeout uses thousanths of a second)
  1396.     duration = (duration*1000);
  1397.     setTimeout('hideStatus()',duration);
  1398. }
  1399.  
  1400.  
  1401. // hides the status div, called after 3 seconds from the showStatus() function
  1402. function hideStatus()
  1403. {
  1404.     // show all the "What's On?" stuff
  1405.     document.getElementById("whats_on_text").style.display="block";
  1406.     document.getElementById("bezel").style.display="block";
  1407.     document.getElementById("arrow").style.display="block";
  1408.     // setting up an AppleAnimator, to fade in the status message speedily
  1409.     // starting from 1 opacity
  1410.     var from=1;
  1411.     // if it's running
  1412.     if(null != statusAnimator)
  1413.     {
  1414.         // stop
  1415.         statusAnimator.stop();
  1416.         // start from current value
  1417.         from = statusAnimator.animations[0].now;
  1418.     }
  1419.     // if the shift key is down, the multiplier is 10, if not, 1
  1420.     var multiplier = globalStatusEvent.shiftKey ? 10 : 1;
  1421.     // upshot of this is, decreasing values get passed to statusFadeHandler
  1422.     statusAnimator = new AppleAnimator(500*multiplier, 13, from, 0, statusFadeHandler);
  1423.     // starts it all off!
  1424.     statusAnimator.start();
  1425. }
  1426.  
  1427.  
  1428. // handles the fading in and out of the status div
  1429. function statusFadeHandler(animation, current, from, to)
  1430. {
  1431.     // set the current opacity to what the Animator passes it
  1432.     document.getElementById("status").style.opacity=current;
  1433.     if (current==0)
  1434.     {
  1435.         // hide the status div (although it's transparent now)
  1436.         document.getElementById("status").style.display="none";
  1437.         // reset message to nothing
  1438.         document.getElementById("status").innerHTML="";
  1439.     }
  1440. }
  1441.  
  1442.  
  1443. // changes the volume when called (when placing a new plugin or when changing
  1444. // the slider position), based on where the slider knob is
  1445. function changeVolume()
  1446. {
  1447.     // get the knob position
  1448.     var sliderPosition = parseInt(document.getElementById("knob").style.left, 10);
  1449.     // the start of the slider is 73 pixels in from the left, so deduct those;
  1450.     // also the slider lingth is 33, so times by 3 to get a value out of 99
  1451.     // (so close!) - RealPlayer sets volume as a percentage, you see
  1452.     var newVolume = (sliderPosition-73)*3
  1453.     // if there's a plugin instance currently placed
  1454.     if (document.getElementById("rp"))
  1455.     {
  1456.         // tell it to set the volume to the new value
  1457.         document.getElementById("rp").SetVolume(newVolume);
  1458.     }
  1459.     // if we're in Dashboard (notice this isn't a nested "if", so this is
  1460.     // regardless of whether we just changed an existing plugin's volume or not)
  1461.     if (window.widget)
  1462.     {
  1463.         // save this as a preference (we'll use it to determine where the slider
  1464.         // should be on next launch)
  1465.         widget.setPreferenceForKey(newVolume,"Volume");
  1466.     }
  1467. }
  1468.  
  1469.  
  1470. // called at launch, to position the slider according to the volume level we've
  1471. // got saved in preferences
  1472. function restoreVolume()
  1473. {
  1474.     // if we're in Dashboard
  1475.     if (window.widget)
  1476.     {
  1477.         // retrieve the preference
  1478.         var newVolume = widget.preferenceForKey("Volume");
  1479.         if (newVolume)
  1480.         {
  1481.             // divide by 3 to get a value out of 33 (the slider length); then
  1482.             // add 73 to this (the slider offset from the left margin) so we get
  1483.             // an absolute position for the knob
  1484.             var sliderPosition = (newVolume/3)+73;
  1485.             // set the knob to this position
  1486.             document.getElementById("knob").style.left = sliderPosition;
  1487.         }
  1488.     }
  1489. }
  1490.  
  1491.  
  1492.  
  1493. /****************************/
  1494. // CALLBACK FROM THE PLUGIN
  1495. /****************************/
  1496.  
  1497.  
  1498. // called when the plugin changes state - eg. starts playing, or stops; is
  1499. // passed the old and new states.
  1500. function OnPlayStateChange(oldState,newState)
  1501. {
  1502.     // if the plugin starts playing
  1503.     if (newState==3)
  1504.     {
  1505.         // stops the indicator
  1506.         stopProgressIndicator();
  1507.     }
  1508.     // otherwise, if the player stops (accidentally)
  1509.     else if (newState==0)
  1510.     {
  1511.         // act as though the pause button was clicked - tidy things up by
  1512.         // killing the progress indicator (if it's going), removing the plugin,
  1513.         // showing the play button again
  1514.         pauseClicked();
  1515.     }
  1516. }
  1517.  
  1518.  
  1519. // Called whenever plugin buffering begins - is passed the type of buffering
  1520. // (startup/network congestion etc), and the percent complete.
  1521. function OnBuffering(flag,percent)
  1522. {
  1523.     // if the buffering results from anything other than startup (we already
  1524.     // have some code handling that scenario, and it's more responsive than
  1525.     // this)
  1526.     if (flag!=0)
  1527.     {
  1528.         // start the progress indicator, for visual feedback
  1529.         startProgressIndicator('large');
  1530.     }
  1531. }
  1532.  
  1533.  
  1534.  
  1535. /*********************************/
  1536. // HIDING AND SHOWING PREFERENCES
  1537. /*********************************/
  1538.  
  1539.  
  1540. // showPrefs() is called when the preferences flipper is clicked upon.  It
  1541. // freezes the front of the widget, hides the front div, unhides the back div,
  1542. // and then flips the widget over.
  1543. function showPrefs()
  1544. {
  1545.     // get the front and back divs
  1546.     var front = document.getElementById("front");
  1547.     var back = document.getElementById("back");
  1548.     // check to see what the latest version is; at intervals of 2 days, this
  1549.     // function will check online, otherwise it uses a local cache of the last
  1550.     // known value.
  1551.     checkForUpdate();
  1552.     // if we're in Dashboard
  1553.     if (window.widget)
  1554.     // We're going to the back where preferences may be changed, so now's a good
  1555.     // time to check the radio station playing by getting its url from the
  1556.     // preferences - this will come in useful later!
  1557.     // NB. Originally used ID, but this fails on UK/INTL streams as they have
  1558.     // the same BBC ID.
  1559.     preferenceBefore = widget.preferenceForKey("url");
  1560.     // freezes the widget so that you can change it without the user noticing
  1561.     widget.prepareForTransition("ToBack");
  1562.     // hide the front
  1563.     front.style.display="none";
  1564.     // show the back
  1565.     back.style.display="block";
  1566.     // and flip the widget over
  1567.     if (window.widget)
  1568.     {  setTimeout ('widget.performTransition();', 0);  }
  1569.     // clean up the front side - hide the circle behind the info button
  1570.     document.getElementById('fliprollie').style.display = 'none';
  1571. }
  1572.  
  1573.  
  1574. // hidePrefs() is called by the done button on the back side of the widget. It
  1575. // performs the opposite transition as showPrefs() does.
  1576. function hidePrefs()
  1577. {
  1578.     // get the front and back
  1579.     var front = document.getElementById("front");
  1580.     var back = document.getElementById("back");
  1581.     // if we're in Dashboard
  1582.     if (window.widget)
  1583.     // now we're going back to the front, so any changes have been made - we
  1584.     // get the radio station just selected, by getting its url from preferences
  1585.     preferenceAfter = widget.preferenceForKey("url");
  1586.     // if a new station has been selected, we change the audio stream playing;
  1587.     // otherwise we leave it alone so that playback isn't interrupted
  1588.     if (preferenceBefore!=preferenceAfter)
  1589.     {
  1590.         // pretend we clicked the play button, to start the audio
  1591.         playClicked(event);
  1592.     }
  1593.     // freezes the widget and prepares it for the flip back to the front
  1594.     widget.prepareForTransition("ToFront");
  1595.     // hide the back
  1596.     back.style.display="none";
  1597.     // show the front
  1598.     front.style.display="block";
  1599.     // and flip the widget back to the front
  1600.     if (window.widget)
  1601.     {    setTimeout ('widget.performTransition();', 0);   }
  1602. }